{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| 死锁类型                                              | 简要定义                                                     | 典型场景/例子                                                | 关键特征                                   | 常见预防/规避策略                                            | 发现与诊断线索                                      |\n",
    "| ----------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------ | ------------------------------------------------------------ | --------------------------------------------------- |\n",
    "| 资源抢占型死锁（Resource Deadlock）                   | 多个线程/进程对可重入资源（锁、内存、文件句柄等）互相持有并等待对方释放 | `Thread A` 持有 `Lock1` 等 `Lock2`，`Thread B` 持有 `Lock2` 等 `Lock1` | Coffman 四条件全部满足；持有-等待-不可抢占 | 统一加锁顺序、一次性申请所有资源、使用定时/尝试加锁、死锁检测与恢复 | 等待图出现环、线程栈卡在 `mutex/lock`               |\n",
    "| 读写锁死锁（RW Lock Deadlock）                        | 多读少写的场景中，读线程持有共享锁并等待升级，写线程等待排他锁 | 读锁升级（Reader-to-Writer upgrade）失败，所有读者互等写者   | 共享锁与排他锁交叉等待                     | 避免读锁升级、使用可重入锁或分离读写阶段                     | 读写锁 API 长期未返回、分析锁依赖图                 |\n",
    "| 条件变量等待死锁（Condition-Variable Deadlock）       | 线程在条件变量上等待，却没有线程能满足条件并唤醒             | 线程 A 等待条件 `cond`，但更新条件的线程 B 正在等待 A 释放锁 | 等待条件无法被满足，或等待循环缺少退出路径 | 使用带谓词的 `wait`/`wait_for`、确保状态更新后再通知         | 线程卡在 `pthread_cond_wait` 等调用，日志无状态变化 |\n",
    "| 信号量死锁（Semaphore Deadlock）                      | 信号量计数耗尽，后续 `P` 操作永久阻塞                        | 多线程 `sem_wait` 同时争抢有限许可；许可被“遗失”             | 信号量计数=0，无释放；可能出现“许可泄漏”   | 确保 `sem_post` 对应 `sem_wait`；使用 RAII、超时等待         | 信号量值长期为 0、多线程停在 `sem_wait`             |\n",
    "| 通信/消息死锁（Communication Deadlock）               | 参与方在同步通信中互等对方消息或回应                         | MPI、Actor 模型中双方 `send` 后 `recv`，互不先读             | 同步通信、双方都在等待输入                 | 采用异步消息、设置超时/优先级、引入缓冲区                    | 通信栈阻塞、消息队列堆积、无进展                    |\n",
    "| 进程/线程创建死锁（Process/Thread Creation Deadlock） | 新线程/进程需要资源才能运行，而资源由运行中的线程持有        | 限制线程并发数时，新线程等资源，老线程等新线程执行           | 资源受限 + 创建流程环路                    | 预留足够资源、分阶段初始化、监控并发限制                     | 创建 API 阻塞、线程数停止增长                       |\n",
    "| I/O 资源死锁（I/O Deadlock）                          | I/O 请求互相等待，导致系统无法前进                           | 多个进程锁定不同磁盘块，互等                                 | I/O 请求不可抢占、依赖次序错乱             | 合理调度 I/O、避免长时间持有 I/O 锁                          | I/O 栈无响应、系统监控显示 I/O 等待                 |\n",
    "| 数据库锁死锁（Database Deadlock）                     | 多事务交叉持有行/表锁互等                                    | 事务 T1 写行 A 再写 B，T2 反序，彼此等待                     | 事务锁粒度、顺序冲突                       | 统一访问顺序、短事务、死锁检测/回滚                          | DB 日志报 deadlock、事务被自动回滚                  |\n",
    "| 内核资源死锁（Kernel Deadlock）                       | 内核态锁/资源互锁，导致系统级 hang                           | 内核模块 A/B 逆序加锁、驱动与内核互等                        | 低层锁依赖无法破除、影响整个系统           | 静态分析锁依赖、禁止嵌套/递归锁                              | 内核日志、NMI dump、无法响应中断                    |\n",
    "| 集群/分布式死锁（Distributed Deadlock）               | 分布式节点间跨资源等待形成循环                               | 分布式数据库、分布式锁服务多节点互相请求                     | 必须借助集中检测或协议；网络延迟使检测困难 | 使用租约、超时、全局死锁检测器                               | 日志出现“deadlock detected”，节点互相排队           |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 条件变量等待型死锁（Condition-Variable Deadlock）"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 工作线程唤醒后又回到了 `while (tasks_.empty())`，在 `isRunning_` 还是 `false` 的情况下再次 `wait`，造成死锁。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "shellscript"
    }
   },
   "outputs": [],
   "source": [
    "! g++ -std=c++17 -pthread deadlock_before.cpp -o deadlock_before"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 修复后的版本（带谓词或显式检查）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "shellscript"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[Worker 0] 启动\n",
      "[Main] main 结束，触发析构\n",
      "[Main] 开始析构线程池\n",
      "[Worker 0] 感知关闭，退出\n",
      "[Main] 析构结束\n"
     ]
    }
   ],
   "source": [
    "! g++ -std=c++17 -pthread deadlock_after.cpp -o deadlock_after && ./deadlock_after"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 死锁程序调试"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "shellscript"
    }
   },
   "outputs": [],
   "source": [
    "! g++ -O0 -g -std=c++17 -pthread deadlock_before.cpp -o deadlock_before"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 运行后死锁卡主\n",
    "```shell\n",
    "$ ./deadlock_before\n",
    "[Worker 0] 启动\n",
    "[Worker 0] 队列空，等待\n",
    "[Main] main 结束，触发析构\n",
    "[Main] 开始析构线程池\n",
    "[Worker 0] 队列空，等待\n",
    "\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "shellscript"
    }
   },
   "outputs": [],
   "source": [
    "! ps -u\n",
    "USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND\n",
    "...\n",
    "ningliu      784  0.0  0.0   2896  1664 pts/11   S+   16:23   0:00 /bin/sh\n",
    "ningliu      855  0.0  0.0   2892  1536 pts/12   S+   16:23   0:00 /bin/sh\n",
    "ningliu    19543  0.0  0.0   7488  3072 pts/9    R+   17:40   0:00 ps -u"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "shellscript"
    }
   },
   "outputs": [],
   "source": [
    "! pidof deadlock_before\n",
    "21838"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| 使用方式                         | 典型操作流程                                                 | 适用场景与注意事项                                           |\n",
    "| -------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |\n",
    "| `gdb -p <PID>`                   | 直接在终端启动：<br>`$ gdb -p 12345`                         | * GDB 启动时就立即尝试附加进程。<br>* 它会从 `/proc/<PID>/exe` 推断可执行文件，但若目标不可访问（例如已被删除或缺乏权限），需要手动 `symbol-file`。<br>* 适合临时调试，少一步进入 GDB 再输入命令。 |\n",
    "| `gdb <program>` + `attach <PID>` | ① 先启动 GDB 并指定二进制：<br>`$ gdb /path/to/prog`<br>② 进入 GDB 后执行：<br>`(gdb) attach 12345` | * 在附加前就能加载符号文件和断点，确保调试信息完整。<br>* 如果程序是通过符号链接、wrapper 或已删除的可执行启动的，这种方式更可靠。<br>* 需要额外一步手动 `attach`，但更灵活，可在一个 GDB 会话里多次附加不同目标。 |"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "shellscript"
    }
   },
   "outputs": [],
   "source": [
    "sudo gdb -p 21838\n",
    "[sudo] password for ningliu: \n",
    "GNU gdb (Ubuntu 12.1-0ubuntu1~22.04.2) 12.1\n",
    "Copyright (C) 2022 Free Software Foundation, Inc.\n",
    "License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n",
    "This is free software: you are free to change and redistribute it.\n",
    "There is NO WARRANTY, to the extent permitted by law.\n",
    "Type \"show copying\" and \"show warranty\" for details.\n",
    "This GDB was configured as \"x86_64-linux-gnu\".\n",
    "Type \"show configuration\" for configuration details.\n",
    "For bug reporting instructions, please see:\n",
    "<https://www.gnu.org/software/gdb/bugs/>.\n",
    "Find the GDB manual and other documentation resources online at:\n",
    "    <http://www.gnu.org/software/gdb/documentation/>.\n",
    "\n",
    "For help, type \"help\".\n",
    "Type \"apropos word\" to search for commands related to \"word\".\n",
    "Attaching to process 21838\n",
    "[New LWP 21839]\n",
    "[Thread debugging using libthread_db enabled]\n",
    "Using host libthread_db library \"/lib/x86_64-linux-gnu/libthread_db.so.1\".\n",
    "__futex_abstimed_wait_common64 (private=0, cancel=true, abstime=0x0, op=393, expected=0, futex_word=0x7fffa3817f58) at ./nptl/futex-internal.c:57\n",
    "57      ./nptl/futex-internal.c: No such file or directory.\n",
    "(gdb) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "shellscript"
    }
   },
   "outputs": [],
   "source": [
    "(gdb) info threads\n",
    "  Id   Target Id                                           Frame \n",
    "* 1    Thread 0x76785dcda3c0 (LWP 35126) \"deadlock_before\" __futex_abstimed_wait_common64 (private=0, cancel=true, abstime=0x0, op=393, expected=0, futex_word=0x7ffc3e0c8db8) at ./nptl/futex-internal.c:57\n",
    "  2    Thread 0x76785d5ff640 (LWP 35127) \"deadlock_before\" __futex_abstimed_wait_common64 (private=0, cancel=true, abstime=0x0, op=393, expected=0, futex_word=0x7ffc3e0c8d8c) at ./nptl/futex-internal.c:57\n",
    "```\n",
    "```shell\n",
    "(gdb) bt\n",
    "#0  __futex_abstimed_wait_common64 (private=0, cancel=true, abstime=0x0, op=393, expected=0, futex_word=0x7ffc3e0c8db8) at ./nptl/futex-internal.c:57\n",
    "#1  __futex_abstimed_wait_common (cancel=true, private=0, abstime=0x0, clockid=0, expected=0, futex_word=0x7ffc3e0c8db8) at ./nptl/futex-internal.c:87\n",
    "#2  __GI___futex_abstimed_wait_cancelable64 (futex_word=futex_word@entry=0x7ffc3e0c8db8, expected=expected@entry=0, clockid=clockid@entry=0, abstime=abstime@entry=0x0, private=private@entry=0) at ./nptl/futex-internal.c:139\n",
    "#3  0x000076785d693a41 in __pthread_cond_wait_common (abstime=0x0, clockid=0, mutex=0x7ffc3e0c8d38, cond=0x7ffc3e0c8d90) at ./nptl/pthread_cond_wait.c:503\n",
    "#4  ___pthread_cond_wait (cond=0x7ffc3e0c8d90, mutex=0x7ffc3e0c8d38) at ./nptl/pthread_cond_wait.c:627\n",
    "#5  0x00005ac3945c5291 in std::condition_variable::wait<ThreadPool::~ThreadPool()::{lambda()#1}>(std::unique_lock<std::mutex>&, ThreadPool::~ThreadPool()::{lambda()#1}) (this=0x7ffc3e0c8d90, __lock=..., __p=...) at /usr/include/c++/11/condition_variable:103\n",
    "#6  0x00005ac3945c4b37 in ThreadPool::~ThreadPool (this=0x7ffc3e0c8d20, __in_chrg=<optimized out>) at deadlock_before.cpp:23\n",
    "#7  0x00005ac3945c4526 in main () at deadlock_before.cpp:72"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "shellscript"
    }
   },
   "outputs": [],
   "source": [
    "(gdb) thread 2\n",
    "[Switching to thread 2 (Thread 0x76785d5ff640 (LWP 35127))]\n",
    "#0  __futex_abstimed_wait_common64 (private=0, cancel=true, abstime=0x0, op=393, expected=0, futex_word=0x7ffc3e0c8d8c) at ./nptl/futex-internal.c:57\n",
    "57      in ./nptl/futex-internal.c"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "shellscript"
    }
   },
   "outputs": [],
   "source": [
    "(gdb) bt\n",
    "#0  __futex_abstimed_wait_common64 (private=0, cancel=true, abstime=0x0, op=393, expected=0, futex_word=0x7ffc3e0c8d8c) at ./nptl/futex-internal.c:57\n",
    "#1  __futex_abstimed_wait_common (cancel=true, private=0, abstime=0x0, clockid=0, expected=0, futex_word=0x7ffc3e0c8d8c) at ./nptl/futex-internal.c:87\n",
    "#2  __GI___futex_abstimed_wait_cancelable64 (futex_word=futex_word@entry=0x7ffc3e0c8d8c, expected=expected@entry=0, clockid=clockid@entry=0, abstime=abstime@entry=0x0, private=private@entry=0) at ./nptl/futex-internal.c:139\n",
    "#3  0x000076785d693a41 in __pthread_cond_wait_common (abstime=0x0, clockid=0, mutex=0x7ffc3e0c8d38, cond=0x7ffc3e0c8d60) at ./nptl/pthread_cond_wait.c:503\n",
    "#4  ___pthread_cond_wait (cond=0x7ffc3e0c8d60, mutex=0x7ffc3e0c8d38) at ./nptl/pthread_cond_wait.c:627\n",
    "#5  0x00005ac3945c4d3f in ThreadPool::worker (this=0x7ffc3e0c8d20, id=0) at deadlock_before.cpp:46\n",
    "#6  0x00005ac3945c736a in std::__invoke_impl<void, void (ThreadPool::*)(int), ThreadPool*, int> (__f=@0x5ac3961a6ee8: (void (ThreadPool::*)(ThreadPool * const, int)) 0x5ac3945c4c36 <ThreadPool::worker(int)>, __t=@0x5ac3961a6ee0: 0x7ffc3e0c8d20)\n",
    "    at /usr/include/c++/11/bits/invoke.h:74\n",
    "#7  0x00005ac3945c7284 in std::__invoke<void (ThreadPool::*)(int), ThreadPool*, int> (__fn=@0x5ac3961a6ee8: (void (ThreadPool::*)(ThreadPool * const, int)) 0x5ac3945c4c36 <ThreadPool::worker(int)>) at /usr/include/c++/11/bits/invoke.h:96\n",
    "#8  0x00005ac3945c71a3 in std::thread::_Invoker<std::tuple<void (ThreadPool::*)(int), ThreadPool*, int> >::_M_invoke<0ul, 1ul, 2ul> (this=0x5ac3961a6ed8) at /usr/include/c++/11/bits/std_thread.h:259\n",
    "#9  0x00005ac3945c713c in std::thread::_Invoker<std::tuple<void (ThreadPool::*)(int), ThreadPool*, int> >::operator() (this=0x5ac3961a6ed8) at /usr/include/c++/11/bits/std_thread.h:266\n",
    "#10 0x00005ac3945c711c in std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (ThreadPool::*)(int), ThreadPool*, int> > >::_M_run (this=0x5ac3961a6ed0) at /usr/include/c++/11/bits/std_thread.h:211\n",
    "#11 0x000076785dadc253 in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6\n",
    "#12 0x000076785d694ac3 in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:442\n",
    "#13 0x000076785d7268c0 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "也就是说，主线程想在析构时等待工作线程结束，而工作线程却在等待“队列有任务或被唤醒”。如果析构函数里没有设置停止标志并通知所有等待的线程，双方就一起睡死在条件变量上，形成你看到的“互相等待”僵局。\n",
    "\n",
    "- `thread 1`: 线程 **Id 1**（主线程）在 `ThreadPool::~ThreadPool()` 的析构逻辑里调用了 `std::condition_variable::wait`，一直卡在 `pthread_cond_wait` → `futex_wait` 上\n",
    "- `thread 2`: 线程 **Id 2**（工作线程）在执行 `ThreadPool::ThreadPool::<lambda>` 的任务循环，同样在 `std::condition_variable::wait` → `pthread_cond_wait` → `futex_wait` 上阻塞。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "shellscript"
    }
   },
   "outputs": [],
   "source": [
    "(gdb) frame 5\n",
    "#5  0x00005ac3945c4d3f in ThreadPool::worker (this=0x7ffc3e0c8d20, id=0) at deadlock_before.cpp:46\n",
    "46                      notEmpty_.wait(lock);\n",
    "(gdb) list\n",
    "41\n",
    "42              while (true) {\n",
    "43                  std::unique_lock<std::mutex> lock(mtx_);\n",
    "44                  while (tasks_.empty()) {  // ❌ 没有检查 isRunning_\n",
    "45                      std::cout << \"[Worker \" << id << \"] 队列空，等待\\n\";\n",
    "46                      notEmpty_.wait(lock);\n",
    "47                  }\n",
    "48\n",
    "49                  if (!isRunning_) {        // ❌ 这一句永远运行不到\n",
    "50                      std::cout << \"[Worker \" << id << \"] 收到关闭信号\\n\";\n",
    "(gdb) info locals\n",
    "lock = {_M_device = 0x7ffc3e0c8d38, _M_owns = true}\n",
    "(gdb) print isRunning_\n",
    "$1 = false"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- `isRunning_` 已经是 `false`（见 `print isRunning_` 的结果），也就是说主线程在析构时已经把线程池标记为“停止”。\n",
    "- 但工作线程仍卡在 `tasks_.empty()` 那个 `while` 里等待 `notEmpty_.wait(lock)`。因为这个等待循环只检查“队列是否为空”，**没有在等待之前检查停止标志**，所以即使 `isRunning_` 为假，它也会继续 `wait()`，永远等不到任务。\n",
    "- 因此 `if (!isRunning_)` 那一行永远无法执行到，线程也就无法跳出循环，导致死锁。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# GCC 调试相关 -f 选项"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| 选项                                                         | 默认是否启用                       | 主要作用                                                     | 典型用途 / 注意事项                                          |\n",
    "| ------------------------------------------------------------ | ---------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |\n",
    "| `-fvar-tracking`                                             | `-g` 时默认启用                    | 记录变量在优化后代码中的位置（寄存器/栈/常量）               | 提升调试器中变量值的准确性；会增加编译时间和调试信息体积     |\n",
    "| `-fvar-tracking-assignments`                                 | `-g` 时默认启用                    | 进一步记录每次变量赋值的位置，使调试器更好地还原源码级变更   | 在高优化级别调试时更容易查看变量值变化；可能显著增加编译开销 |\n",
    "| `-fvar-tracking-assignments-toggle`                          | 默认禁用                           | 在函数内动态切换 `-fvar-tracking-assignments`，供调试器实验或内部测试 | 很少在日常构建中使用，主要用于 GCC 调试或实验场景            |\n",
    "| `-fdebug-macro`                                              | 默认禁用                           | 在 DWARF 调试信息中记录宏展开细节                            | 使用 `gdb` 查看宏展开、条件编译时的真实代码；增加调试信息大小 |\n",
    "| `-fdebug-prefix-map=旧=新`                                   | 默认禁用                           | 在调试信息中替换源码路径前缀                                 | 保持可重复构建、隐藏绝对路径；常用于构建系统或容器环境       |\n",
    "| `-fdebug-types-section`                                      | 默认启用（某些平台）               | 将调试类型信息放入独立的 ELF section，提升调试信息复用       | 使多个目标文件共享类型描述，减小整体大小；需要调试器支持     |\n",
    "| `-feliminate-unused-debug-types` / `-fno-eliminate-unused-debug-types` | 默认启用                           | 删除未用到的调试类型定义，减小调试段体积                     | 若出现调试器缺少类型信息，可用 `-fno-eliminate-unused-debug-types` 保留它们 |\n",
    "| `-femit-class-debug-always`                                  | 默认禁用                           | 即使类/结构体未被实例化也生成完整调试信息                    | 在模板或未实例化类型调试时有用；显著增加调试信息体积         |\n",
    "| `-femit-class-debug-limits=<n>`                              | 缺省值为 0（无限制）               | 限制为某个类/结构体生成的调试信息数量                        | 控制调试信息大小，避免因极大模板类型导致编译变慢             |\n",
    "| `-femit-struct-debug-baseonly` / `-femit-struct-debug-reduced` / `-femit-struct-debug-detailed` | 默认 `-femit-struct-debug-reduced` | 控制为结构体/类生成调试信息的详尽程度                        | `baseonly` 仅含基类信息；`reduced` 为默认折衷；`detailed` 生成最完整信息（体积最大） |\n",
    "| `-fkeep-inline-functions` / `-fkeep-static-functions`        | 默认禁用                           | 保留被优化掉的内联/静态函数体，便于调试                      | 调试时可查看被内联的函数；增加代码尺寸与调试信息量           |\n",
    "\n",
    "> **提示**：大多数选项仅在同时指定 `-g` 时才有意义；开启更多调试信息会提高编译时间、占用更多磁盘空间。根据调试需求和构建时间在不同配置中进行取舍。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 单独的调试文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "vscode": {
     "languageId": "shellscript"
    }
   },
   "outputs": [],
   "source": [
    "! g++ -O2 -g -std=c++17 -pthread deadlock_before.cpp -o deadlock_before"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "vscode": {
     "languageId": "shellscript"
    }
   },
   "outputs": [],
   "source": [
    "! objcopy --only-keep-debug deadlock_before deadlock_before.debug && \n",
    "strip --strip-debug --strip-unneeded deadlock_before && \n",
    "objcopy --add-gnu-debuglink=deadlock_before.debug deadlock_before"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "vscode": {
     "languageId": "shellscript"
    }
   },
   "outputs": [],
   "source": [
    "! objcopy --add-gnu-debuglink=deadlock_before.debug deadlock_before"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 查看 debuglink 是否写入成功"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "shellscript"
    }
   },
   "outputs": [],
   "source": [
    "! readelf -n deadlock_before | grep -A3 GNU_DEBUGLINK # ?"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "C++17",
   "language": "C++17",
   "name": "xcpp17"
  },
  "language_info": {
   "codemirror_mode": "text/x-c++src",
   "file_extension": ".cpp",
   "mimetype": "text/x-c++src",
   "name": "c++",
   "version": "17"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
